Odtwarzanie i rejestrowanie dźwięku na niskim poziomie. I [WinApi][CBuilder]

Krecik
[WinApi][CppBuilder] Odtwarzanie i rejestrowanie dźwięku na niskim poziomie. <mmsystem.h> PART I

Napisałem ten artykuł, ponieważ nie mogłem swego czasu w polskim internecie znaleźć konkretnych informacji na temat "ręcznego" przetwarzaniu dźwięku (sample by sample, czyli operowaiu na najmniejszych jednostkach dźwięku cyfrowego w PCM [PulseCodeModulation] - próbkach).

Przy okazji postanowiłem, chociaż jedną nogą, wrócić na łamy tego servisu [co po starsi userzy ;), mam nadzieję, jeszcze mnie pamiętą - pozdrowienia :-)].

Seria będzie składała się z trzech części:

  1. Omówienie formatu Wave w systemie Windows (w tym trochę o RIFF) <small>[TEN ARTYKUŁ]</small>
  2. O odtwarzaniu dźwięku przez funkcje waveOut...
  3. Nagrywanie dźwięku przez waveIn...

//----------------------------------------------------------------

Microsoft opracował ogólną strukturę plików do wymiany danych (głównie multimedia) w systemie Windows. Ogólna nazwa standardu to RIFF (Resource Interchange File Format). Plik taki składa się z większej liczby pakietów (Chunk) mających własne nazwy. Nazwa pakietu musi składać się z czterech znaków, w tym co najmniej jedna litera (pozostałe znaki muszą być wtedy spacjami). Spacja nie może rozdzielać liter w nazwie pakietu. Nazwy pisane dużymi literami (jak RIFF, WAVE, ...) muszą być zarejestrowane przez Microsoft, by były standardowe i jednoznaczne. Po nazwie pakietu następuje 4-bajtowa liczba określająca długość pakietu (bez 4 bajtów nazwy i 4 bajtów określających długość pakietu).

Co prawda API systemu Windows (w bibliotece mmsystem.h) zawiera specjalne funkcje do obsługi plików zapisanych w formacie RIFF (jak mmioOpen, mmioDescent,itp...), jednak uznałem, że w wypadku dość prostego formatu wave nie będę ich opisywał (materiał na osobny artykuł).

Prostszym i w tym wypadku chyba klarowniejszym sposobem jest stworzenie struktury nagłówka pliku Wave i wczytanie do niej danych z pliku.

Wewnętrzną budowę tego formatu można by przedstawić następująco:
<small>
Sorry, ale przesuncie sobie text parę linijek w dół, bo kochany skrypcik tak robi z tabelkami... :(
[To się jakoś dało ominąć, ale nie pamiętam jak, a jestem teraz baaardzo śpięcy i zaraz padnę twarzą w keyboard.]</small>

offsetnazwa polawielkość w bajtach, typopis
0 ChunkID 4, char[4] Podstawowy pakiet w pliku {'R','I','F','F'} (zawiera dwie podgrupy: "fmt " i "data")
4 ChunkSize 4, unsigned long Określa rozmiar pakietu RIFF
8 Format 4, char[4] Określa format pliku {'W','A','V','E'}
12 Subchunk1ID 4, char[4]/td> Nazwa pakietu zawierającego dane o formacie dźwięku (jakość) {'f','m','t',' '}
16 Subchunk1Size 4, unsigned long Rozmiar pakietu
20 AudioFormat 2, unsigned short Format dźwieku (1-bez kompresji, standard)
22 NumChannel 2, unsigned short Liczba kanałów (1-mono, 2-stereo)
24 SampleRate 4, unsigned long Szybkość próbkowania
28 ByteRate 4, unsigned long Liczba bajtów na sekundę... Wyzncznik jakości i czasem służy do wyznaczania czasu trwania dźwięku
32 BlockAlign 2, unsigned short (NumChannels * BitsPerSample) / 8
34 BitsPerSample 2, unsigned short Ilość bitów przypadających na każdą próbkę dźwięku
36 Subchunk2ID 4, char[4] Nazwa pakietu z danymi {'d','a','t','a'}
40 Subchunk2Size 4, unsigned long Ilość danych... Rozmiar dźwięku</td
44 data Subchunk2Size, char[Subchunk2Size] Kolejne próbki dźwięku...
</span></p>

Muszę tu się jeszcze wytłumaczyć... Tak naprawdę powyższe przedstawienie pomija kilka pól, ponieważ nie występują one w standardowym formacie zapisu (bez kompresji), ale uprzedzam, że mogą się pojawić w jakichś niestandardowych formatach.
Jak już wspomniałem istnieją funkcje w mmsystem, które bardzo ładnie operują na typie RIFF, ale nam tylko zaciemniłyby program.
Ja przedstawiam inny sposób, strukturę o takich polach jak wymagane przez standard...
W języku C++ mogła by ona wyglądać np. tak (dodatkowy podział na 3 wewnętrzne sekcje jest mój własny)

typedef struct _WAVEHEAD {
 struct {
        char            RIFF[4];
        unsigned long   Size;
        char            WAVE[4];
 } RIFF;
 struct {
        char            fmt[4] ;
        unsigned long   BlockSize;
        struct {
          unsigned short  AudioFormat;
          unsigned short  NumChannels;
          unsigned long SampleRate;
          unsigned long ByteRate;
          unsigned short  BlockAlign;
          unsigned short  BitsPerSample;
          } Format;
 } fmt;
 struct {
        char            data[4];
        unsigned long   DataSize;
 } data;
} WAVEHEAD;

a przykładowa funkcja wczytująca tą strukturę z pliku, np tak:

#include <stdio.h>
#include <mmsystem.h>

WAVEHEAD head;        //nasza struktura
WAVEFORMATEX format;  //trochę z wyprzedzeniem, ponieważ ta struktura
                      //  pochodzi z mmsystem i będzie dotyczyć kolejnych art. 
char *FileName = "c:\\blabla.wav";
FILE *plik = fopen(FileName,"r");
fread(&head, sizeof(WAVEHEAD), 1, plik);
memcpy(&format,&(head.fmt.Format),sizeof(WAVEFORMATEX));

buffer = new char[head.data.DataSize]; // i zaś się trochę do przodu wyrwałem, bo to są już dane dźwięku, które wykorzystamy wkrótce
fread(buffer,head.data.DataSize,1,plik);
fclose(plik);

delete[] Buffer;




No i to by było na tyle w dniu dzisiejszym... Kolejny art (odtwarzanie dźwięku) w najbliższej przyszłości... Pozdrawiam...

PS> Bardzo przepraszam za ewentualne błędy ort!, naprawde nie mam nigdzie pod bliższą i dalszą ręką MSWord`a... Sorry... Poprawie jak tylko znajdę kolejną chwilę wolnego czasu.

3 komentarzy

właśnie poszukuje takiego programu do tej tabelki w C++ na komponentach może ma ktoś taki niech napisze z góry dziękuje pozdro !!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Sorry, ale przesuncie sobie text parę linijek w dół, bo kochany skrypcik tak robi z tabelkami... :(
[To się jakoś dało ominąć, ale nie pamiętam jak, a jestem teraz baaardzo śpięcy i zaraz padnę twarzą w keyboard.]
Widać, że dość dawno nie byłeś na 4p :) Pewne zmiany w skryptach zaszły widać :)

postawiłem 5 na zachęte - mysle, że coś z tego będzie :D